﻿
namespace BLPicker.OnTheFly
{
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.Diagnostics.Contracts;
    using System.Linq;
    using System.Reflection;
    using BLPicker;
    using BLPicker.OnTheFly.Tools;
    using BLPicker.OnTheFly.Tools.Common;

    public class OnTheFlyProxyFactory : IBusinessLayerProxyFactory
    {

        private readonly ConcurrentDictionary<Type, Assembly> _compiledAssemblies = new ConcurrentDictionary<Type, Assembly>();

        private const string GenerateAssembliesDomain = "BLPicker.GeneratedAssemblies";
        private const string CompiledAssemblyNameTemplate = "Generated.{0}.dll";
        private const string CompiledNameSpace = "BLPicker";
        private const string CompiledProxyClassNameTemplate = "BusinessLayerProxyFor{0}";

        public TBusinessLayerInterface GetProxy<TBusinessLayerInterface>(IBusinessLayerFactory<TBusinessLayerInterface> businessLayerFactory)
        {
            Type businessLayerType = typeof(TBusinessLayerInterface);
            if (!businessLayerType.IsInterface)
                throw new Exception("TBusinessLayerInterface must be an interface");

            string nameSpace = CompiledNameSpace;
            string className = string.Format(CompiledProxyClassNameTemplate, businessLayerType.GetSimpleName());
            string assemblyName = string.Format(CompiledAssemblyNameTemplate, string.Format("{0}.{1}", nameSpace, className));

            BusinessLayerProxyActivator proxyActivator = new BusinessLayerProxyActivator();

            Assembly compiledAssembly;
            if (_compiledAssemblies.TryGetValue(businessLayerType, out compiledAssembly))
                return proxyActivator.CreateBusinessLayerProxyInstance<TBusinessLayerInterface>(
                    compiledAssembly.GetType(string.Format("{0}.{1}", nameSpace, className)), 
                    businessLayerFactory);

            List<Assembly> referencedAssemblies = new List<Assembly>();
            referencedAssemblies.Add(typeof(IBusinessLayerFactory<>).Assembly);

            ProxySourceCodeBuilder sourceCodeBuilder = new ProxySourceCodeBuilder();
            sourceCodeBuilder.Build(businessLayerType, nameSpace, className);
            referencedAssemblies.AddRange(sourceCodeBuilder.ReferencedAssemblies);
            string sourceCode = sourceCodeBuilder.SourceCode;

            SourceCodeCompiler sourceCodeCompiler = new SourceCodeCompiler();

            StringCollection output;

            compiledAssembly = sourceCodeCompiler
               .DynamicCompile(
                   sourceCode,
                   assemblyName,
                   referencedAssemblies.Select(assembly => assembly.Location),
                   false,
                   true,
                   out output);

            if (compiledAssembly == null)
                throw new Exception("Compilation error", new Exception(string.Join("\n", output.OfType<string>())));

            _compiledAssemblies.TryAdd(businessLayerType, compiledAssembly);

            return proxyActivator.CreateBusinessLayerProxyInstance<TBusinessLayerInterface>(
                compiledAssembly.GetType(string.Format("{0}.{1}", nameSpace, className)),
                businessLayerFactory);
        }
    }
}
